using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security;

namespace Pinwheel.Toolkits
{
    public static partial class Native
    {
		[SuppressUnmanagedCodeSecurity]
		public abstract class PInvokeUtility : Utility
		{
			private static void VerifyHandle(ObjectHandle handle)
			{
				if (handle.Address == IntPtr.Zero)
				{
					throw new InvalidOperationException("ObjectHandle.Address is null");
				}
				if (handle.Identifier != 0L)
				{
					throw new InvalidOperationException("ObjectHandle.Identifier has to be 0");
				}
			}

			public override ObjectHandle HandleAcquire(object obj)
			{
				return new ObjectHandle(GCHandle.Alloc(obj, GCHandleType.Pinned).AddrOfPinnedObject());
			}

			public override void HandleRelease(ObjectHandle handle)
			{
				VerifyHandle(handle);
				GCHandle.FromIntPtr(handle.Address).Free();
			}

			public override object GetObjectForHandle(ObjectHandle handle)
			{
				VerifyHandle(handle);
				return GCHandle.FromIntPtr(handle.Address).Target;
			}
		}

		[SuppressUnmanagedCodeSecurity]
		public abstract class PInvokeAllocator : Allocator
		{
			private Dictionary<IntPtr, IntPtr> _alignmentMapping = new Dictionary<IntPtr, IntPtr>();

			public sealed override unsafe void* Alloc(int count)
			{
				if (count == 0)
				{
					return null;
				}
				void* ptr = (void*)Marshal.AllocHGlobal((IntPtr)count);
				TrackAlloc(ptr);
				return ptr;
			}

			public sealed override unsafe void* Alloc(int count, int alignment)
			{
				byte* ptr = (byte*)(void*)Marshal.AllocHGlobal(count + alignment);
				long address = (long)ptr;
				long addressDiff = address % alignment;
				long addressAdd = alignment - addressDiff;
				TrackAlloc(ptr);
				Assert.Check(addressAdd >= 0 && addressAdd <= alignment);
				if (addressDiff != 0)
				{
					lock (_alignmentMapping)
					{
						_alignmentMapping.Add(new IntPtr(ptr + addressAdd), new IntPtr(ptr));
					}
					return ptr + addressAdd;
				}
				return ptr;
			}

			public sealed override unsafe void Free(void* ptr)
			{
				if (_alignmentMapping.Count > 0)
				{
					lock (_alignmentMapping)
					{
						if (_alignmentMapping.Count > 0 && _alignmentMapping.TryGetValue(new IntPtr(ptr), out var intPtr))
						{
							ptr = intPtr.ToPointer();
						}
					}
				}
				TrackFree(ptr);
				Marshal.FreeHGlobal((IntPtr)ptr);
			}
		}
    }
}
